# -*- coding: utf-8 -*-

import cryptacular.bcrypt

from sqlalchemy import Table
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import delete
from sqlalchemy import update
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref
from sqlalchemy.orm import column_property
from sqlalchemy.orm import synonym
from sqlalchemy.orm import joinedload
from sqlalchemy.types import Integer
from sqlalchemy.types import Text
from sqlalchemy.types import Unicode
from sqlalchemy.types import UnicodeText
from sqlalchemy.types import DateTime
from sqlalchemy.types import Boolean
from sqlalchemy.types import LargeBinary
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base

import os
from datetime import datetime    

from zope.sqlalchemy import ZopeTransactionExtension

from pyramid.security import Everyone
from pyramid.security import Authenticated
from pyramid.security import Allow

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

crypt = cryptacular.bcrypt.BCRYPTPasswordManager()


__all__ = ['Usuario', 'Rol', 'Permiso','Fase', 'Proyecto', 'TipoItem', 'AtributosPorTipoItem']


#{ Association tables


# This is the association table for the many-to-many relationship between
# groups and permissions. This is required by repoze.what.
permiso_por_rol = Table('tbl_permiso_por_rol', Base.metadata,
    Column('id_rol', Integer, ForeignKey('tbl_rol.id_rol',
        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
    Column('id_permiso', Integer, ForeignKey('tbl_permisos.id_permiso',
        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
)

# This is the association table for the many-to-many relationship between
# groups and members - this is, the memberships. It's required by repoze.what.
rol_por_usuario = Table('tbl_rol_por_usuario', Base.metadata,
    Column('user_id', Integer, ForeignKey('tbl_users.user_id',
        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
    Column('id_rol', Integer, ForeignKey('tbl_rol.id_rol',
        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
)

def hash_password(password):
    return unicode(crypt.encode(password))


class User(Base):
    """
    Application's user model.
    """
    __tablename__ = 'tbl_users'
    user_id = Column(Integer, primary_key=True)
    username = Column(Unicode(20), unique=True)
    name = Column(Unicode(50))
    apellido = Column(Unicode(50))
    email = Column(Unicode(50))
    telefono= Column(Integer, default='0')
    creado = Column(DateTime, default=datetime.now)
    _password = Column('password', Unicode(60))


    def _get_password(self):
        return self._password

    def _set_password(self, password):
        self._password = hash_password(password)

    password = property(_get_password, _set_password)
    password = synonym('_password', descriptor=password)

    def __init__(self, username, password, name, apellido, telefono, email):
        self.username = username
        self.name = name
        self.email = email
        self.apellido = apellido
        self.password = password
        self.telefono = telefono
        

    @classmethod
    def get_by_username(cls, username):
        return DBSession.query(cls).filter(cls.username == username).first()
        
    @classmethod
    def eliminar_user(cls, user_id):
		usuario = DBSession.query(User).filter_by(user_id=user_id).one()
		DBSession.delete(usuario)
    
    @classmethod
    def modificar_user(cls, user_id, username, name, apellido, email):
		usuario = DBSession.query(User).filter_by(user_id=user_id).one()
		#~ usuario.user_id = user_id
		#~ usuario.username = username
		#~ usuario.name = name
		usuario.user_id = user_id
		usuario.username = username
		usuario.name = name
		DBSession.query(User).filter_by(user_id = user_id).update({"username":username, "name": name, "apellido": apellido, "email": email})
		
    
    @classmethod    
    def get_all_user(cls):		
        return DBSession.query(User).all()

    @classmethod
    def check_password(cls, username, password):
        user = cls.get_by_username(username)
        if not user:
            return False
        return crypt.check(user.password, password)


class Rol(Base):
    """
    Group definition for :mod:`repoze.what`.

    Only the ``group_name`` column is required by :mod:`repoze.what`.
    Pero se usa un 'translation' a "nombre_rol" y "usuarios"
    """

    __tablename__ = 'tbl_rol'

    #{ Columns

    id_rol = Column(Integer, autoincrement=True, primary_key=True)
    codigo = Column(Unicode(50), unique=True)
    nombre_rol = Column(Unicode(70), nullable=False)
    descripcion = Column(Unicode(100))
    creado = Column(DateTime, default=datetime.now)
    tipo = Column(Unicode(50), nullable=True) #esto se debe cambiar a false
    
    #Para relacionar un rol con un recurso especifico
    
    id_proyecto = Column(Integer, ForeignKey('tbl_proyecto.id_proyecto',
                                             ondelete="CASCADE",
                                             onupdate="CASCADE"))
    id_fase = Column(Integer, ForeignKey('tbl_fase.id_fase',
                                         ondelete="CASCADE",
                                         onupdate="CASCADE"))
    id_tipo_item = Column(Integer, ForeignKey('tbl_tipo_item.id_tipo_item',
                                              ondelete="CASCADE",
                                              onupdate="CASCADE"))
	
    #{ variables
    #template para el codigo (usar metodo format)
    
    tmpl_codigo = u"rol-{id_rol}-{tipo}"
    tipos_posibles = {u'Plantilla proyecto' : 'plant-proy',
                      u'Plantilla fase' : 'plant-fase',
                      u'Plantilla tipo item' : 'plant-ti', 
                      u'Sistema' : 'sis',
                      u'Proyecto' : 'proy',                        
                      u'Fase' : 'fase', 
                      u'Tipo de Item' : 'ti'}
	
    #{ Relations

    user = relation('User', secondary=rol_por_usuario, backref='roles')
    
    def __init__(self,nombre_rol,descripcion):
		self.nombre_rol=nombre_rol
		self.descripcion=descripcion

    @classmethod
    def eliminar_rol(cls, id_rol):
		rol = DBSession.query(Rol).filter_by(id_rol=id_rol).one()
		DBSession.delete(rol)

    @classmethod
    def modificar_rol(cls, id_rol, nombre_rol, descripcion):
		rol = DBSession.query(Rol).filter_by(id_rol=id_rol).one()		
		rol.id_rol = id_rol
		rol.nombre_rol = nombre_rol
		rol.descripcion = descripcion
		DBSession.query(Rol).filter_by(id_rol = id_rol).update({"nombre_rol":nombre_rol, "descripcion": descripcion})
		
    
    @classmethod    
    def get_all_rol(cls):		
        return DBSession.query(Rol).all()
    

class Permiso(Base):
    """
    Permission definition for :mod:`repoze.what`.

    Only the ``permission_name`` column is required by :mod:`repoze.what`.
    Pero se usa un 'translation' a "nombre_permiso"

    """

    __tablename__ = 'tbl_permisos'

    #{ Columns

    id_permiso = Column(Integer, autoincrement=True, primary_key=True)
    nombre_permiso = Column(Unicode(32), unique=True, nullable=False)
    descripcion = Column(Unicode(100))
    tipo = Column(Unicode(60))
    #{ Relations

    roles = relation(Rol, secondary=permiso_por_rol,
                      backref='permisos')
                      

class Fase(Base):
    """
    Clase que define un Fase de Proyecto.
    """
    __tablename__ = 'tbl_fase'
    
    #{ Columnas
    id_fase = Column(Integer, autoincrement=True, primary_key=True)
    id_proyecto = Column(Integer, ForeignKey('tbl_proyecto.id_proyecto',
                                             ondelete="CASCADE",
                                             onupdate="CASCADE"))
    codigo = Column(Unicode(50), unique=True)
    posicion = Column(Integer, nullable=False)
    nombre = Column(Unicode(32), nullable=False)
    descripcion = Column(Unicode(200), nullable=True)
    numero_items = Column(Integer, nullable=False, default=0)
    numero_lb = Column(Integer, nullable=False, default=0)
    estado = Column(Unicode(20), nullable=True, default=u"Inicial")

    # template para codificacion
    tmpl_codigo = u"fase-{posicion}/p-{id_proyecto}"
    estados_posibles = [u'Inicial', u'Desarrollo', u'Completa', 'Comprometida']
    #{ Relaciones
    items = relation('Item', cascade="delete")
    roles = relation('Rol', cascade="delete")
    tipos_de_item = relation('TipoItem', cascade="delete")


class Proyecto(Base):
    """
    Clase que define un Proyecto que sera administrado por el sistema.
    """
    __tablename__ = 'tbl_proyecto'
    
    #{Columnas
    id_proyecto = Column(Integer, autoincrement=True, primary_key=True)
    codigo = Column(Unicode(50), unique=True)
    nombre = Column(Unicode(32), nullable=False, unique=True)
    descripcion = Column(Unicode(200), nullable=False, default=u"Proyecto PDM")
    fecha_creacion = Column(DateTime, nullable=False, default=datetime.now)
    complejidad_total = Column(Integer, nullable=True, default=0)
    estado = Column(Unicode(20), nullable=True, default=u"No Iniciado")#debe ser false
    numero_fases = Column(Integer, nullable=False, default=0)
    
    #template para codificacion
    tmpl_codigo = u"proy-{id_proyecto}"
    estados_posibles = [u'Iniciado', u'No iniciado']
    
    #{ Relaciones
    fases = relation('Fase', cascade="delete")
    tipos_de_item = relation('TipoItem', cascade="delete")
    roles = relation('Rol', cascade="delete")
    #}

    def __init__(self,nombre,descripcion,numero_fases):
    	self.nombre=nombre
    	self.descripcion=descripcion
    	self.numero_fases=numero_fases

    @classmethod
    def eliminar_proyecto(cls, id_proyecto):
		proyecto = DBSession.query(Proyecto).filter_by(id_proyecto=id_proyecto).one()
		DBSession.delete(proyecto)

    @classmethod
    def modificar_proyecto(cls, id_proyecto, nombre, descripcion,numero_fases):
		proyecto = DBSession.query(Proyecto).filter_by(id_proyecto=id_proyecto).one()		
		proyecto.id_proyecto = id_proyecto
		proyecto.nombre = nombre
		proyecto.descripcion = descripcion
		DBSession.query(Proyecto).filter_by(id_proyecto = id_proyecto).update({"nombre":nombre, "descripcion": descripcion,"numero_fases":numero_fases})
		
    
    @classmethod    
    def get_all_proyecto(cls):		
        return DBSession.query(Proyecto).all()


class TipoItem(Base):
    """
    Clase que define las caracteristicas
    de un tipo de Item.
    """
    __tablename__ = 'tbl_tipo_item'
    
    #{ Columnas
    id_tipo_item = Column(Integer, autoincrement=True, primary_key=True)
    codigo = Column(Unicode(50), unique=True)
    nombre = Column(Unicode(50), nullable=False)
    descripcion = Column(Unicode(200), nullable=True)
    id_proyecto = Column(Integer, ForeignKey('tbl_proyecto.id_proyecto',
                                             ondelete="CASCADE"), nullable=True)
    id_fase = Column(Integer, ForeignKey('tbl_fase.id_fase',
                                         ondelete="CASCADE"), nullable=True)
    id_padre = Column(Integer, ForeignKey('tbl_tipo_item.id_tipo_item',
                                          ondelete="CASCADE"), nullable=True)
    
    # template para codificacion
    tmpl_codigo = u"{siglas}-ti-{id}/f-{pos}/p-{proy}"
    #{ Relaciones
    hijos = relation('TipoItem', cascade="delete")
    atributos = relation('AtributosPorTipoItem', cascade="delete")
    items = relation('Item', cascade="delete")
    roles = relation('Rol', cascade="delete")
    #}
    
    
    def __init__(self,nombre,descripcion):
		self.nombre=nombre
		self.descripcion=descripcion

    @classmethod
    def eliminar_tipo_item(cls, id_tipo_item):
		tipo_item = DBSession.query(TipoItem).filter_by(id_tipo_item=id_tipo_item).one()
		DBSession.delete(tipo_item)

    @classmethod
    def modificar_tipo_item(cls, id_tipo_item, nombre, descripcion):
		tipo_item = DBSession.query(TipoItem).filter_by(id_tipo_item=id_tipo_item).one()		
		tipo_item.id_tipo_item = id_tipo_item
		tipo_item.nombre = nombre
		tipo_item.descripcion = descripcion
		DBSession.query(TipoItem).filter_by(id_tipo_item = id_tipo_item).update({"nombre":nombre,
		"descripcion": descripcion})
		
    
    @classmethod    
    def get_all_tipo_item(cls):		
        return DBSession.query(TipoItem).all()

class AtributosPorTipoItem(Base):
    """
    Clase que define que atributos posee un determinado
    tipo de item.
    """
    __tablename__ = 'tbl_atributos_por_tipo_item'
    
    #{ Columnas
    id_atributos_por_tipo_item = Column(Integer, autoincrement=True, 
                                        primary_key=True)
    nombre = Column(Unicode(32), nullable=False)
    tipo = Column(Unicode(32), nullable=False)
    valor_por_defecto = Column(Unicode(32), nullable=True)
    id_tipo_item = Column(Integer, ForeignKey('tbl_tipo_item.id_tipo_item'))
    #}
    _tipos_permitidos = [u"Numerico", u"Texto", u"Fecha"]
                              

class Item(Base):
    """
    Clase que representa al Item en su version
    actual
    """
    __tablename__ = 'tbl_item'
    
    #{ Columnas
    id_item = Column(Integer, autoincrement=True, primary_key=True)
    codigo = Column(Unicode(50), unique=True)
    numero = Column(Integer, nullable=False)
    numero_por_tipo = Column(Integer, nullable=False)
    id_tipo_item = Column(Integer,
                          ForeignKey('tbl_tipo_item.id_tipo_item',
                                      onupdate='CASCADE', ondelete='CASCADE'),
                          nullable=False)
    id_fase = Column(Integer, ForeignKey('tbl_fase.id_fase', 
                                         onupdate='CASCADE', ondelete='CASCADE'),
                     nullable=False)
    id_propiedad_item = Column(Integer)

    
    #template para codificacion
    #tmpl_codigo = u"IT-{id_item}-TI-{id_tipo_item}"
    tmpl_codigo = u"{siglas}-{numero_por_tipo}/f-{pos}/p-{proy}"
    estados_posibles = {'d' : u"Desaprobado",
                        'a' : u"Aprobado",
                        'b' :  u"Bloqueado",
                        'e' : u"Eliminado",
                        'r-b' : u"Revision-Bloq",
                        'r-d' : u"Revision-Desbloq"
                        }
    #{ Relaciones
    propiedad_item_versiones = relation('PropiedadItem', cascade="delete")
    
    #}


class PropiedadItem(Base):
    """
    Clase que contiene los atributos basicos de 
    un L{Item}
    """
    __tablename__ = 'tbl_propiedad_item'
    

    
    #{ Columnas
    id_propiedad_item = Column(Integer, autoincrement=True, primary_key=True)
    version = Column(Integer, nullable=False)
    complejidad = Column(Integer, nullable=False)
    prioridad = Column(Integer, nullable=False)
    estado = Column(Unicode(20), nullable=False)
    descripcion = Column(Unicode(200), nullable=True, default=u"DESC")
    observaciones = Column(Unicode(200), nullable=True, default=u"OBS")
    id_item_actual = Column(Integer, ForeignKey('tbl_item.id_item',
                                                onupdate='CASCADE', 
                                                ondelete='CASCADE'))
    
    #{ Relaciones
    relaciones = relation('RelacionPorItem', cascade="delete")
    archivos = relation('ArchivosPorItem', cascade="delete")
    atributos = relation('AtributosPorItem', cascade="delete")
    #}
    
    
class RelacionPorItem(Base):
    """
    Clase que asocia los items con sus relaciones
    """
    __tablename__ = 'tbl_relacion_por_item'
    
    #{Columnas
    id_relacion_por_item = Column(Integer, autoincrement=True, 
                                  primary_key=True)
    id_propiedad_item = Column(Integer, 
                               ForeignKey('tbl_propiedad_item.id_propiedad_item',
                                          onupdate='CASCADE', ondelete='CASCADE'))
    id_relacion = Column(Integer, ForeignKey('tbl_relacion.id_relacion',
                                             onupdate='CASCADE', ondelete='CASCADE'))
    revisar = Column(Boolean, nullable=False, default=False)
   
    #{ Relaciones
    
    # Segun la pagina de SqlAlchemy
    #Many to one places a foreign key in the parent table referencing the 
    #child. The mapping setup is identical to one-to-many, however SQLAlchemy 
    #creates the relationship as a scalar attribute on the parent object 
    #referencing a single instance of the child object.
    
    #relaciones = relation("Relacion")
    relacion = relation("Relacion", cascade="delete")    


class Relacion(Base):
    """
    Clase que define una relacion entre items.
    """
    __tablename__ = 'tbl_relacion'
    
    #{ Columnas
    id_relacion = Column(Integer, autoincrement=True, primary_key=True)
    tipo = Column(Unicode(45), nullable=False)
    codigo = Column(Unicode(50), unique=True)
    id_anterior = Column(Integer, ForeignKey('tbl_item.id_item',
                                              onupdate='CASCADE', 
                                              ondelete='CASCADE'))
    id_posterior = Column(Integer, ForeignKey('tbl_item.id_item',
                                              onupdate='CASCADE', 
                                              ondelete='CASCADE'))
    
    tipo_relaciones = {'a-s': u"Antecesor-Sucesor", 'p-h' : u"Padre-Hijo"}
    #template para codificacion
    tmpl_codigo = u"rel-{id_relacion}-{tipo}-{id_anterior}-{id_posterior}"


class AtributosDeItems(Base):
    """
    Clase que define el valor para un atributo del item. Dicho atributo
    forma parte del conjunto de atributos del L{TipoItem}.
    """
    __tablename__ = 'tbl_atributos_de_items'
    
    #{ Columnas
    id_atributos_de_items = Column(Integer, 
                                   autoincrement=True, primary_key=True)
    id_atributos_por_tipo_item = Column(Integer,
        ForeignKey('tbl_atributos_por_tipo_item.id_atributos_por_tipo_item',
                   onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False)
    _valor = Column(Unicode(200), nullable=False)


class AtributosPorItem(Base):
    """
    Clase que asocia un item con sus atributos
    """
    __tablename__ = 'tbl_atributos_por_item'
    
    #{ Columnas
    id_atributos_por_item = Column(Integer, autoincrement=True, 
                                   primary_key=True)
    id_propiedad_item = Column(Integer,
        ForeignKey('tbl_propiedad_item.id_propiedad_item',
                   onupdate='CASCADE', ondelete='CASCADE'))
    id_atributos_de_items = Column(Integer,
        ForeignKey('tbl_atributos_de_items.id_atributos_de_items',
                    onupdate='CASCADE', ondelete='CASCADE'))

    #{ Relaciones
    atributo = relation("AtributosDeItems")
    #}


class ArchivosExternos(Base):
    """
    Clase que representa un archivo externo que puede ser adjuntado a un
    L{Item}
    """
    __tablename__ = 'tbl_archivos_externos'
    
    #{ Columnas
    id_archivo_externo = Column(Integer, autoincrement=True, primary_key=True)
    archivo = Column(LargeBinary, nullable=False)
    nombre_archivo = Column(Unicode(50), nullable=False)


class ArchivosPorItem(Base):
    """
    Clase que asocia un item con sus archivos externos
    """
    __tablename__ = 'tbl_archivos_por_item'
    
    #{ Columnas
    id_archivos_por_item = Column(Integer, autoincrement=True, primary_key=True)
    id_propiedad_item = Column(Integer, 
        ForeignKey('tbl_propiedad_item.id_propiedad_item',
                   onupdate='CASCADE', ondelete='CASCADE'))  #parte izquierda de la relacion
    id_archivo_externo = Column(Integer,
        ForeignKey('tbl_archivos_externos.id_archivo_externo',
                   onupdate='CASCADE', ondelete='CASCADE')) #parte derecha de la relacion
    
    #{ Relaciones
    archivo = relation("ArchivosExternos")


class HistorialItems(Base):
    """
    Clase que define un historial de modificaciones
    hechas sobre los items.
    """
    __tablename__ = 'tbl_historial_item'
    
    #{ Columnas
    id_historial_items = Column(Integer, autoincrement=True, primary_key=True)
    tipo_modificacion = Column(Unicode(100), nullable=False)
    fecha_modificacion = Column(DateTime, nullable=False, default=datetime.now)
    user_id = Column(Integer, ForeignKey('tbl_users.user_id',
                                            onupdate='CASCADE', 
                                            ondelete='CASCADE'))
    id_item = Column(Integer, 
        ForeignKey('tbl_propiedad_item.id_propiedad_item',
                    onupdate='CASCADE', ondelete='CASCADE'))
    
    #{ Relaciones
    user = relation("User", backref="historial_item")
    item = relation("PropiedadItem", backref="historial_item")


        
class RootFactory(object):
    __acl__ = [
        (Allow, Everyone, 'view'),
        (Allow, Authenticated, 'post')
    ]

    def __init__(self, request):
        pass  # pragma: no cover        
        

def initialize_sql(engine):  # pragma: no cover
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)                

